﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Serialization;

namespace FuiouCode {
    public static class Utils {
        /// <summary>
        /// 过滤不需要的字段
        /// </summary>
        /// <param name="map">参数集合</param>
        /// <param name="encryption">加密方式：MD5/MD5withRSA</param>
        /// <returns></returns>
        public static Dictionary<string, object> ParaFilter(Dictionary<string, object> map, string encryption) {
            SortedList<string, object> sorteds = new SortedList<string, object>(map);
            Dictionary<string, object> result = new Dictionary<string, object>();

            if (map == null || map.Count() <= 0) {
                return default;
            }

            foreach (var m in sorteds) {
                if (m.Key.Equals("sign", StringComparison.CurrentCultureIgnoreCase)) {
                    continue;
                }

                if (encryption.Equals("MD5withRSA") && m.Key.Length >= 8 && m.Key.Substring(0, 8).Equals("reserved", StringComparison.CurrentCultureIgnoreCase)) {
                    continue;
                }

                if (encryption.Equals("MD5") && string.IsNullOrEmpty(m.Value + "")) {
                    continue;
                }

                if (m.Value is List<Dictionary<string, object>>) {
                    List<Dictionary<string, object>> values = new List<Dictionary<string, object>>();
                    foreach (var v in m.Value as List<Dictionary<string, object>>) {
                        values.Add(ParaFilter(v, encryption));
                    }
                    result.Add(m.Key, values);
                } else {
                    result.Add(m.Key, m.Value);
                }
            }


            return result;
        }

        /// <summary>
        /// 创建link字符串
        /// </summary>
        /// <param name="map">参数集合</param>
        /// <param name="format">报文格式：xml/son</param>
        /// <returns></returns>
        public static string CreateLinkstring(Dictionary<string, object> map, string format) {
            string prestr = "";

            foreach (var m in map) {
                if (format.Equals("XML")) {
                    prestr = $"{prestr}{m.Key}={m.Value}&";
                } else if (format.Equals("JSON")) {
                    if (m.Value is List<Dictionary<string, object>>) {
                        string param = "";

                        foreach (var v in m.Value as List<Dictionary<string, object>>) {
                            param = $"{param}{CreateJsonString(v)},";
                        }

                        if (!string.IsNullOrEmpty(param)) {
                            if (param.EndsWith(",")) {
                                param = param.TrimEnd(new char[] { ',' });
                            }

                            prestr = $"{prestr}{m.Key}=[{param}]&";
                        }
                    } else {
                        prestr = $"{prestr}{m.Key}={m.Value}&";
                    }
                }
            }

            if (prestr.EndsWith("&")) {
                prestr = prestr.TrimEnd(new char[] { '&' });
            }

            return prestr;
        }

        public static string CreateJsonString(Dictionary<string, object> map) {
            string strResult = "";

            if (map != null) {
                foreach (var m in map) {
                    strResult += $"{m.Key}:{m.Value},";
                }

                if (strResult.EndsWith(",")) {
                    strResult = strResult.TrimEnd(new char[] { ',' });
                }
            }

            return "{" + strResult + "}";
        }

        /// <summary>
        /// 加签
        /// </summary>
        /// <param name="map">参数集合</param>
        /// <param name="key">秘钥</param>
        /// <param name="charset">编码格式</param>
        /// <param name="encryption">加密方式：MD5/MD5withRSA</param>
        /// <param name="format">报文格式：xml/son</param>
        /// <returns></returns>
        public static string GetSign(Dictionary<string, object> map, string key, string charset, string encryption = "MD5withRSA", string format = "XML") {
            Dictionary<string, object> mapNew = ParaFilter(map, encryption);

            string preSignStr = CreateLinkstring(mapNew, format);

            Console.WriteLine("==============================待签名字符串==============================\r\n" + preSignStr);

            string sign = "";

            if (encryption.Equals("MD5withRSA")) {
                sign = RSAFromPkc8.sign(preSignStr, key, charset);
            } else if (encryption.Equals("MD5")) {
                sign = MD5Encrypt32(preSignStr + "&key=" + key, charset);
            }

            sign = sign.Replace("\r\n", "");

            Console.WriteLine("==============================签名字符串==============================\r\n" + sign);

            return sign;
        }

        /// <summary>
        /// 验签
        /// </summary>
        /// <param name="result">待验签参数</param>
        /// <param name="key">秘钥</param>
        /// <param name="charset">编码格式</param>
        /// <param name="encryption">加密方式：MD5/MD5withRSA</param>
        /// <param name="format">报文格式：xml/json</param>
        /// <returns></returns>
        public static bool CheckSign(string result, string key, string charset, string encryption = "MD5withRSA", string format = "XML") {
            Dictionary<string, object> map = null;
            bool verify = false;

            if (format.Equals("XML")) {
                XmlDocument doc = new XmlDocument();

                doc.LoadXml(result);

                if (doc.HasChildNodes) {
                    map = XmlToMap(doc.ChildNodes);
                }
            } else if (format.Equals("JSON")) {
                map = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(result);
            }

            if (map != null) {
                if (!map.IsSet("sign")) {
                    return true;
                }

                if (map.TryGetValue("sign", out object objSign)) {
                    string sign = objSign.ToString();

                    map.Remove("sign");

                    map = ParaFilter(map, encryption);

                    string preSignStr = CreateLinkstring(map, format);
                    Console.WriteLine("==============================待验签字符串==============================\r\n" + preSignStr);

                    if (encryption.Equals("MD5withRSA")) {
                        verify = RSAFromPkc8.verify(preSignStr, sign, key, charset);
                    } else if (encryption.Equals("MD5")) {
                        verify = MD5Encrypt32(preSignStr + "&key=" + key, charset).Equals(sign);
                    }
                }
            }

            return verify;
        }

        /// <summary>
        /// UrlEncode
        /// </summary>
        /// <param name="temp"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static string UrlEncode(string temp, Encoding encoding) {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < temp.Length; i++) {
                string t = temp[i].ToString();
                string k = HttpUtility.UrlEncode(t, encoding);
                if (t == k) {
                    stringBuilder.Append(t);
                } else {
                    stringBuilder.Append(k.ToUpper());
                }
            }
            return stringBuilder.ToString();
        }

        /// <summary>
        /// 获取随机码
        /// </summary>
        /// <param name="num"></param>
        /// <returns></returns>
        public static string GetRandom(int num) {
            StringBuilder checkCode = new StringBuilder();
            string[] codes = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
                                 "A", "B", "C", "D", "E", "F", "G", "H", "I",
                                 "J", "K", "L", "M", "N", "O", "P", "Q", "R",
                                 "S", "T", "U", "V", "W", "X", "Y", "Z"};

            Random random = new Random();
            for (int i = 0; i < num; i++) {
                int number = random.Next(codes.Length - 1);
                string code = codes[number];
                checkCode.Append(code);
            }
            return checkCode.ToString();
        }

        /// <summary>
        /// 把Xml转成对应的实体
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="str"></param>
        /// <param name="charset"></param>
        /// <returns></returns>
        public static T XmlDeserialize<T>(string str, string charset) where T : class {
            try {
                object obj;

                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

                using (MemoryStream mem = new MemoryStream(Encoding.GetEncoding(charset).GetBytes(str))) {
                    using (XmlReader reader = XmlReader.Create(mem)) {
                        XmlSerializer formatter = new XmlSerializer(typeof(T));
                        obj = formatter.Deserialize(reader);
                    }
                }
                return obj as T;
            } catch (Exception ex) {
                Console.WriteLine(ex.Message + (ex.InnerException != null ? ex.InnerException.Message : ""));
                return default;
            }
        }

        public static Dictionary<string, object> XmlToMap(XmlNodeList nodes) {
            try {
                Dictionary<string, object> map = null;
                foreach (XmlNode node in nodes) {
                    if (node.NodeType == XmlNodeType.XmlDeclaration)
                        continue;

                    if (node.Name.Equals("xml") && node.HasChildNodes) {
                        map = XmlToMap(node.ChildNodes);
                    } else {
                        if (map == null) {
                            map = new Dictionary<string, object>();
                        }

                        map.Add(node.Name, node.InnerText + "");
                    }
                }

                return map;
            } catch (Exception ex) {
                Console.WriteLine(ex.Message + (ex.InnerException != null ? ex.InnerException.Message : ""));
            }

            return default;
        }

        #region 对象转成字典
        /// <summary>
        /// 对象转换为字典
        /// </summary>
        /// <param name="obj">待转化的对象</param>
        /// <param name="format">报文格式：xml/json</param>
        /// <returns></returns>
        public static Dictionary<string, object> ObjectToMap<T>(T obj, string format = "XML") {
            Dictionary<string, object> map = new Dictionary<string, object>();

            Type t = obj.GetType(); // 获取对象对应的类， 对应的类型

            PropertyInfo[] pi = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); // 获取当前type公共属性

            foreach (PropertyInfo p in pi) {
                string name = p.Name;
                MethodInfo m = p.GetGetMethod();
                bool ignore = false;

                if (m != null && m.IsPublic) {
                    var attrs = p.GetCustomAttributes(false);

                    foreach (var attr in attrs) {
                        var typename = attr.GetType().Name;

                        if (typename.Equals("FuiouRequestNameAttribute")) {
                            FuiouRequestNameAttribute fuiouRequestName = attr as FuiouRequestNameAttribute;

                            if (format.Equals("XML")) {
                                name = fuiouRequestName.XmlName;
                            } else if (format.Equals("JSON")) {
                                name = fuiouRequestName.JsonName;
                            }
                        } else if (typename.Equals("DataMemberAttribute")) {
                            DataMemberAttribute dataMember = attr as DataMemberAttribute;

                            name = dataMember.Name;
                        } else if (typename.Equals("XmlElementAttribute")) {
                            XmlElementAttribute xmlElement = attr as XmlElementAttribute;

                            name = xmlElement.ElementName;
                        } else if (typename.Equals("XmlIgnoreAttribute")) {
                            ignore = true;
                        }
                    }

                    if (!ignore) {
                        if (p.PropertyType.IsGenericType) {
                            Type ptype = p.PropertyType;

                            if (Array.IndexOf(ptype.GetInterfaces(), typeof(IEnumerable)) > -1) {
                                object op = p.GetValue(obj);

                                if (op != null) {
                                    IEnumerable en = op as IEnumerable;

                                    List<Dictionary<string, object>> list = new List<Dictionary<string, object>>();

                                    foreach (object eo in en) {
                                        list.Add(ObjectToMap(eo));
                                    }

                                    map.Add(name, list);
                                }
                            }
                        } else {
                            object o = p.GetValue(obj);

                            map.Add(name, o + ""); // 向字典添加元素
                        }
                    }
                }
            }
            return map;
        }
        #endregion

        /// <summary>
        /// 判断键/值是否存在
        /// </summary>
        /// <param name="pairs"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool IsSet(this Dictionary<string, object> pairs, string key) {
            if (pairs.ContainsKey(key)) {
                pairs.TryGetValue(key, out object value);

                if (!string.IsNullOrEmpty(value + "")) {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 设置键/值
        /// </summary>
        /// <param name="pairs"></param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public static void SetValue(this Dictionary<string, object> pairs, string key, string value) {
            if (pairs.ContainsKey(key)) {
                pairs[key] = value;
            } else {
                pairs.Add(key, value);
            }
        }

        public static string MD5Encrypt32(this string s, string charset) {
            MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
            byte[] data = md5Hasher.ComputeHash(Encoding.GetEncoding(charset).GetBytes(s));
            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++) {
                sBuilder.Append(data[i].ToString("x2"));//转化为小写的16进制
            }
            return sBuilder.ToString();
        }


        public static string MapToXml(this Dictionary<string, object> map, string key, string charset, string encryption = "MD5withRSA") {
            Dictionary<string, object> reqs = new Dictionary<string, object>();

            foreach (KeyValuePair<string, object> kvp in map) {
                reqs.Add(kvp.Key, kvp.Value);
            }

            string sign = GetSign(reqs, key, charset, encryption);
            reqs.Remove("sign");
            reqs.Add("sign", sign);

            XmlDocument doc = new XmlDocument();
            XmlElement elem = doc.CreateElement("xml");
            doc.AppendChild(elem);

            foreach (KeyValuePair<string, object> kvp in reqs) {
                XmlElement elem2 = doc.CreateElement(kvp.Key);
                elem2.InnerText = kvp.Value + "";
                elem.AppendChild(elem2);
            }

            return doc.OuterXml;
        }
    }
}
